package robocup.component.situations;

import java.util.LinkedList;

import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.STAMINA_LEVEL;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Circle;
import robocup.component.geometry.Vektor;
import robocup.component.math.RCMath;
import robocup.component.worldmodel.PlayersModel.SORTED;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Line;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;
import robocup.component.worldobjects.Player.ROLE;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * The dribble situation.<br>
 * 
 * This situation matches worlds where the player controls the ball. It tries to
 * find out the best dribbling action and the benefit for that action. <br>
 * Even without opponents (fast) dribbling can be a complicated procedure. With
 * opponent(s) very near we nearly always prefer passing as this situation is
 * still unfinished and works far from perfect :-)! <br>
 * <br>
 * 
 * TODO - this is only patchwork, seems not buggy but should be improved
 */
public class DribbleSituation
    extends Situation {

  private final static String DEBUG_SLOWDRIBBLING  = "slow";

  private final static String DEBUG_FASTDRIBBLING  = "fast";

  private final static String DEBUG_TURNING        = "turning";

  private final static String DEBUG_WAITWITHBALL   = "waitWithBall";

  private final static String DEBUG_SAVINGSECURE   = "savingSec";

  private final static String DEBUG_SAVINGUNSECURE = "savingUnsec";

  private final static String DEBUG_OUTPLAYGOALIE  = "outplayGoalie";

  private final static String DEBUG_STOPBALL       = "stopBall";

  private final static String DEBUG_CIRCULATEBALL  = "circulateBall";

  private static final String DEBUG_KICK_TO_FRONT  = "frontKick";

  private static final String DEBUG_KICK_TO_SIDE   = "sideKick";

  private static final String DEBUG_WE_ARE_OUT     = "weAreOut";

  // the max security (see SituationEvaluator)
  public final static int     maxSecurity          = 4;

  private static final String DEBUG_NOT_NEAR_GOAL  = "notNearGoal";

  private static final String DEBUG_CLEAR          = "clear";

  private static final boolean DEBUG2 = false;

  // Dribble Types
  public enum TYPE {
    // not implemnted yet
    BACKWARD_DRIBBLING,

    // dribbling with no opponents in dribble direction
    FAST_DRIBBLING,

    // slow and secure dribbling
    SLOW_DRIBBLING,

    // change dribble dir
    TURNING,

    // wait with the ball at the current position
    WAIT_WITH_BALL,

    // Opponent can get the ball in 1, but we can save it
    SAVING_SECURE,

    // Opponent can get the ball in 1 and we can't save it
    SAVING_UNSECURE
  }

  /**
   * a tackling player = player + tackle actions + tackleprob(action) +
   * resulting_players(action)
   * 
   * used for storage.
   */
  private class TacklingPlayer {

    // the player
    private final Player                         player;

    // all resulting players when trying to tackle
    private final LinkedList<Player>             p_tackling  = new LinkedList<Player>();

    // all actions that can lead to tackling (can be 1 for Vi and 2 for BS)
    private final LinkedList<LinkedList<Action>> actions     = new LinkedList<LinkedList<Action>>();

    // the tackle probs
    private final LinkedList<Double>             tackleProbs = new LinkedList<Double>();

    /**
     * Constructor.
     * 
     * @param p -
     *          the player
     */
    public TacklingPlayer(
        final Player p) {

      this.player = p;
    }

    /**
     * adds the data.
     * 
     * @param p_before_tackling -
     *          the player after acting but before tackling
     * @param actions_before_tackling -
     *          the actions done by the player to reach the tackle possibility
     * @param tackleProb -
     *          the resulting tackle probability
     */
    public void addTacklePossibility(final Player p_before_tackling,
        final LinkedList<Action> actions_before_tackling,
        final double tackleProb) {

      this.p_tackling.add(p_before_tackling);
      this.actions.add(actions_before_tackling);
      this.tackleProbs.add(new Double(tackleProb));
    }

    /**
     * @return Returns the actions.
     */
    public LinkedList<LinkedList<Action>> getActions() {

      return this.actions;
    }

    /**
     * @return Returns the p_tackling.
     */
    public LinkedList<Player> getP_tackling() {

      return this.p_tackling;
    }

    /**
     * @return Returns the player.
     */
    public Player getPlayer() {

      return this.player;
    }

    /**
     * @return Returns the tackleProbs.
     */
    public LinkedList<Double> getTackleProbs() {

      return this.tackleProbs;
    }
  }

  // dummy objects
  private final Player                     dummyPlayer     = Player
                                                               .getDummyInstance();

  private Vektor                           dummyVektor     = Vektor
                                                               .getDummyInstance();

  private final Vektor                     dummyVektor2    = Vektor
                                                               .getDummyInstance();

  private final DashAction                 dummyDashAction = new DashAction(0);

  private final DashAction                 dash100         = new DashAction(100);

  private final DashAction                 dash50          = new DashAction(50);

  // the next self (without acting)
  private final Player                     nextSelf        = Player
                                                               .getDummyInstance();

  // the next ball (without kicking)
  private final Ball                       nextBall        = Ball
                                                               .getDummyInstance();

  // the opps ordered by distance
  private Player[]                         opponentsByDistance;

  // buffer for different opponents
  private final LinkedList<Player>         oppsInBodyDir   = new LinkedList<Player>();

  private final LinkedList<Player>         oppsInGoalDir   = new LinkedList<Player>();

  private final LinkedList<Player>         oppsInBackDir   = new LinkedList<Player>();

  private final LinkedList<Player>         oppsNear        = new LinkedList<Player>();

  private final LinkedList<Player>         oppsRelevant    = new LinkedList<Player>();

  private final LinkedList<Player>         oppsRight       = new LinkedList<Player>();

  private final LinkedList<Player>         oppsLeft        = new LinkedList<Player>();

  // opponents that can tackle in 1
  private final LinkedList<TacklingPlayer> oppsTackling    = new LinkedList<TacklingPlayer>();

  // last dribbling type
  private TYPE                             lastType        = TYPE.WAIT_WITH_BALL;

  // benefit for passing
  private final double                     passBenefit     = 0.0;

  // the benefit for scoring
  private double                           scoreBenefit;

  // first guess dribbling benefit
  private double                           defaultBenefit  = 0.0;

  private final boolean                    DEBUG           = false;

  // true if there is a opponent that can tackle the ball in the next cycle
  private boolean                          opponentCanTackleIn1;

  // the best benefit of the other states by now (Max(KickNRun, Passing))
  private double                           bestBenefit;

  // the best point to save the ball when an opponent is near enough to tackle
  private Vektor                           mostSecurePoint = Vektor
                                                               .getDummyInstance();

  // the point to dribble to
  private final Vektor                     pointToGo       = Vektor
                                                               .getDummyInstance();

  /**
   * Constructor - a situation, which predicts the effectiveness of different
   * dribble alternatives.
   * 
   * @param world -
   *          the current world-model
   * @param prophet -
   *          the prediction device and parent object of this
   * @param movements -
   *          a generic movement model
   */
  public DribbleSituation(
      final WorldModel world,
      final Prophet prophet,
      final Movements movements) {

    super(world, prophet, movements);

  }

  /*
   * (non-Javadoc)
   * 
   * @see robocup.component.situations.Situation#update()
   */
  public void update() {

    super.update();

    this.reset();

    if (this.shouldBeCalculated()) {
      this.calculate();

      this.checkForTackling();
    }
  }

  /**
   * the situation is not calculated if the passing or scoring benefit is to
   * high.
   * 
   * @return
   */
  private boolean shouldBeCalculated() {

    boolean b = this.defaultBenefit > this.bestBenefit * 0.9;
    if (this.DEBUG && !b) {
      System.out
          .println(this.getWorld().getCycleAndPlayerString() + " DribbeSituation. No calculation as benefit is already to high.  Best:" + this.bestBenefit + " My:" + this.defaultBenefit);
    }
    return b;

  }

  /**
   * reset all values
   */
  private void reset() {

    this.bestAction = null;
    this.benefit = 0.0;
    this.defaultBenefit = 0.0;

    this.opponentsByDistance = this.getWorld().getPlayersSortedBy(
        SORTED.EXPECTED_OPPONENTS_BY_DISTANCE_ASC,
        this.getWorld().getSelfRef().getPosition());

    this.oppsInBackDir.clear();
    this.oppsInBodyDir.clear();
    this.oppsInGoalDir.clear();
    this.oppsRelevant.clear();
    this.oppsNear.clear();
    this.oppsLeft.clear();
    this.oppsRight.clear();
    this.oppsTackling.clear();

    this.mostSecurePoint.setEmpty();

    this.nextBall.copy(this.getWorld().getBallRef());
    this.nextBall.extrapolate(1);

    this.nextSelf.copy(this.getWorld().getSelfRef());
    this.nextSelf.predictThisAfterAction(null);

    this.setBestDribbleDir();

    // use passbenefit as a limit
    // this.passBenefit = this.getProphet().getPassBenefit();
    this.scoreBenefit = this.getProphet().getScoreBenefit();

    // this.bestBenefit = this.passBenefit;
    this.bestBenefit = Math.max(this.benefit, this.scoreBenefit);
  }

  /**
   * main method which decides which sub methods should be invoked
   * 
   */
  private void calculate() {

    if (this.DEBUG) {
      System.out
          .println(this.getWorld().getCycleAndPlayerString() + " Calculate Dribbling (Best: " + this.bestBenefit + " Passing:" + this.passBenefit + ")");
    }

    // filter unimportant opponents
    this.loadOpponents();

    // if we can dash opponents might assume we dash and the ball remains as
    // it is so we assume that they try to tackle the next ball
    if (this.nextballCanBeReached()) {
      this.opponentCanTackleIn1 = this.opponentCanTackleIn1(this.oppsNear,
          this.nextBall.getPosition());
    }

    // if we have to kick the ball in order to stop it we assume that
    // opponents will turn against our position instead against the next ball
    // which they don't know about
    else {
      this.opponentCanTackleIn1 = this.opponentCanTackleIn1(this.oppsNear,
          this.nextSelf.getPosition());
    }

    // TODO special dribbling against goalie
    if (((this.oppsInBodyDir.size() > 0) && this.oppsInBodyDir.get(0)
        .isGoalie()) || ((this.oppsInGoalDir.size() > 0) && this.oppsInGoalDir
        .get(0).isGoalie()) || ((this.oppsRelevant.size() > 0) && this.oppsRelevant
        .get(0).isGoalie())) {
      this.subStateString = DribbleSituation.DEBUG_OUTPLAYGOALIE;
      this.outplayGoalie();
    }

    // turn when in wrong direction
    else if (this.gotWrongBodyDir() && ((this.oppsNear.size() == 0) || (((this.oppsInBodyDir
        .size() > 0) && (this.oppsInBodyDir.get(0).getDistance(this.nextBall) < 4)) && (this.oppsNear
        .get(0).getDistance(this.nextSelf.getPosition()) > 3)))) {
      this.subStateString = DribbleSituation.DEBUG_TURNING;
      this.doTurning();
    }

    else {
      final double distanceToGoal = this.nextSelf.getDistance(SConf
          .getInstance().GOAL_POS_OTHER);

      // wait if no stamina left and no teammate joined the attack
      if (((distanceToGoal > 30) && (this.nextSelf.getStamina() < DAInamiteConstants.STAMINA_THR_MID_LOW)) || (this.nextSelf
          .getStamina() < DAInamiteConstants.STAMINA_THR_LOW + 100)) {
        this.subStateString = DribbleSituation.DEBUG_WAITWITHBALL;
        this.waitWithBall();
      }
      // dribble fast if no opponent in the way
      else if ((this.oppsInBodyDir.size() == 0) || (this.oppsInBodyDir.get(0)
          .getDistance(this.nextSelf) > 5)) {
        this.subStateString = DribbleSituation.DEBUG_FASTDRIBBLING;
        this.doFastDribbling();
      }
      // dribble slow otherwise
      else {
        this.subStateString = DribbleSituation.DEBUG_SLOWDRIBBLING;
        this.doSlowDribbling();
      }
    }
    // check wether the action we intend to do leaves the opponent a tackle
    // possiblity
    this.checkForTackling();

    // check wether the bestAction would kick the ball outside
    this.checkBallOutside();

    // dashing is prefered to turning as it gets us nearer to the dribbling
    // position
    if ((this.bestAction != null) && (this.bestAction.getType() == Action.TYPE.DASH)) {
      this.benefit *= 1.1;
      if (this.DEBUG) {
        System.out.println("No Dash -> benefit set to " + this.benefit);
      }
    }
  }

  /**
   * finds out the best dribble dir by checking the benefit of each dir.
   * 
   */
  private void setBestDribbleDir() {

    double angleAbs;
    double benefit;
    int security = 5;

    // if we are already dribbling in the best dir, turning is punished
    boolean isAlreadyBestDir = true;

    // the bestpos to dribble to depends on the players role
    Vektor bestPos;

    if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
        .getRoleByRoleNumber())) {
      bestPos = SConf.getInstance().GOAL_POST_LEFT_OTHER;
    }
    else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
        .getRoleByRoleNumber())) {
      bestPos = SConf.getInstance().GOAL_POST_RIGHT_OTHER;
    }
    else {
      bestPos = SConf.getInstance().GOAL_POS_OTHER;
    }

    // deciding if we are dribbling in a good direction

    // logic for penalty area
    if (this.getWorld().inOpponentsPenalty(this.nextSelf) || (this.nextSelf
        .getPosition().y > 40)) {
      if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
          .getRoleByRoleNumber())) {
        this.dummyVektor.x = 0;
        this.dummyVektor.y = 38;
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(),
            SConf.getInstance().GOAL_POST_LEFT_OTHER, this.dummyVektor);
      }
      else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
          .getRoleByRoleNumber())) {
        this.dummyVektor.x = 0;
        this.dummyVektor.y = 38;
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(), this.dummyVektor,
            SConf.getInstance().GOAL_POST_RIGHT_OTHER);
      }
      else {
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(),
            SConf.getInstance().GOAL_POST_LEFT_OTHER,
            SConf.getInstance().GOAL_POST_RIGHT_OTHER);
      }
    }

    // logic for outside penalty area
    else {
      if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
          .getRoleByRoleNumber())) {
        this.dummyVektor.x = -SConf.getInstance().half_width + 3;
        this.dummyVektor.y = SConf.getInstance().half_length;
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(), this.dummyVektor,
            SConf.getInstance().GOAL_POS_OTHER);

        this.dummyVektor.pointAtPolar(8, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());

        if ((this.dummyVektor.x < -SConf.getInstance().half_width + 2) && (this.nextSelf
            .getBodyDir() > -30)) {
          isAlreadyBestDir = true;
        }
      }
      else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
          .getRoleByRoleNumber())) {
        this.dummyVektor.x = SConf.getInstance().half_width - 3;
        this.dummyVektor.y = SConf.getInstance().half_length;
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(), SConf.getInstance().GOAL_POS_OTHER,
            this.dummyVektor);

        this.dummyVektor.pointAtPolar(8, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());

        if ((this.dummyVektor.x > SConf.getInstance().half_width - 2) && (this.nextSelf
            .getBodyDir() < 30)) {
          isAlreadyBestDir = true;
        }
      }
      else {
        isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
            this.nextSelf.getPosition(),
            SConf.getInstance().GOAL_POST_LEFT_OTHER,
            SConf.getInstance().GOAL_POST_RIGHT_OTHER);
      }
    }

    if (this.DEBUG) {
      System.out
          .print("Trying to find best dir: alreadyBest = " + isAlreadyBestDir + " ");
    }

    // how often would we need for turning and how much time will the opponent
    // give us, turning is most often punished as we prefer dashing
    double turnToGoalFact = 1.0;
    if (!isAlreadyBestDir) {
      int timeForTurning = this.oppsInGoalDir.size() == 0 ? 10 : (int) this.oppsInGoalDir
          .get(0).getDistance(this.nextSelf) - 1;
      if (this.nextballCanBeReached()) {
        timeForTurning--;
      }
      else {
        timeForTurning -= 2;
      }

      if (timeForTurning > 3) {
        turnToGoalFact = Math.pow(0.8, timeForTurning - 3);
      }
    }

    final double angleToGoalAbs = this.nextSelf.getAngleTo(bestPos);

    // try to find the benefit of dribbling in the driection of each
    // angle using the SituationEvaluator
    for (double angle = -180; angle <= 180; angle += DAInamiteConstants.DRIBBLE_TURN_ANGLE) {
      angleAbs = Vektor.normalize(angle + this.getWorld().getSelfRef()
          .getBodyDir());
      this.dummyVektor.pointAtPolar(2, angleAbs);
      this.dummyVektor.addToThis(this.nextSelf.getPosition());

      this.dummyVektor2.pointAtPolar(5, angleAbs);
      this.dummyVektor2.addToThis(this.nextSelf.getPosition());

      if (this.getWorld().inField(this.dummyVektor2)) {

        final Player opp = this.getWorld().getClosestOpponentTo(
            this.dummyVektor);

        security = opp == null ? DribbleSituation.maxSecurity : Math.min(
            (int) this.dummyVektor.getDistance(opp.getPosition()) - 1,
            DribbleSituation.maxSecurity);

        benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.DRIBBLING,
            this.getWorld().getBallRef().getPosition(), this.dummyVektor,
            security, 1, this.getWorld().getSelfRef());

        // punishing turning if dribbling in a good dir
        if ((angle != 0) && isAlreadyBestDir) {
          benefit *= 0.8;
        }

        if ((Math.abs(Vektor.normalize(angleToGoalAbs - angleAbs)) > 30) && !isAlreadyBestDir) {
          benefit *= turnToGoalFact;
        }

        if (benefit > this.defaultBenefit) {
          this.defaultBenefit = benefit;
          this.pointToGo.copy(this.dummyVektor);
          if (this.DEBUG) {
            System.out
                .print("\t" + this.pointToGo + ":" + this.defaultBenefit + ":" + turnToGoalFact);
          }
        }

      }
    }

    if (this.DEBUG) {
      System.out.println();
    }
  }

  /**
   * sets the relevant opponents and filters all others.
   * 
   */
  private void loadOpponents() {

    double distance;
    double angleToBody;
    final double angleToGoal = this.getWorld().getSelfRef().getAngleRel(
        SConf.getInstance().GOAL_POS_OTHER, RELATIVE.TO_BODY);
    double maxAngle;

    for (final Player opp : this.opponentsByDistance) {
      distance = this.getWorld().getSelfRef().getDistance(opp);
      if (distance > 15) {
        break;
      }
      else {
        this.oppsRelevant.add(opp);

        angleToBody = this.getWorld().getSelfRef().getAngleRel(opp,
            RELATIVE.TO_BODY);
        maxAngle = this.getAngleForDistance(distance);

        if (maxAngle > Math.abs(angleToBody)) {
          this.oppsInBodyDir.add(opp);
        }

        if (maxAngle > Math.abs(Vektor.normalize(180.0 - angleToBody))) {
          this.oppsInBackDir.add(opp);
        }

        if (maxAngle > Math.abs(Vektor.normalize(angleToGoal - angleToBody))) {
          this.oppsInGoalDir.add(opp);
        }

        if ((angleToBody < 90 + maxAngle) && (angleToBody > 90 - maxAngle)) {
          this.oppsRight.add(opp);
        }

        if ((angleToBody > -90 - maxAngle) && (angleToBody < -90 + maxAngle)) {
          this.oppsLeft.add(opp);
        }

        if (distance < 4.5) {
          this.oppsNear.add(opp);
        }

      }
    }

  }

  /**
   * help method for telling which opponents to consider. Returns the angle to
   * consider the opponent as "in the way" which may differ depending on the
   * opponent's distance.
   * 
   * @param distance -
   *          the opponents distance
   * @return the angle to consider the opponent as "in the way"
   */
  private double getAngleForDistance(final double distance) {

    if (distance > 6) {
      return 45.0;
    }

    if (distance > 3) {
      return 60.0;
    }

    return 75.0;
  }

  /**
   * checks wether an opponent can tackle a point. Opponent which can tackle
   * this point are added to the tackler list.
   * 
   * @param opponents -
   *          the opponents to consider
   * @return true if an opponent can tackle in 1 cycle when the ball remains as
   *         it is (2 cycles if based on bs)
   */
  private boolean opponentCanTackleIn1(final LinkedList<Player> opponents,
      final Vektor point) {

    Action a;
    Action b;

    double tackleProb = 0.0;

    // the world is vi based. opponent has 1 action before tackling
    // check turning and dashing
    if (this.getWorld().isViBased()) {
      double tackleProbTurning = 0.0;
      double tackleProbDashing = 0.0;

      for (final Player opp : opponents) {
        final TacklingPlayer tackler = new TacklingPlayer(opp);
        final LinkedList<Action> actions = new LinkedList<Action>();

        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);

        tackleProbTurning = this.dummyPlayer.tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbTurning);

        if (tackleProb > 0) {
          actions.add(a);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);
          if (this.DEBUG) {
            System.out
                .println("Opponent can tackle in 1 Prob:" + tackleProbTurning + " maxProb:" + tackleProb + " Action:" + a);
          }
        }

        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);

        tackleProbDashing = this.dummyPlayer.tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbDashing);

        if (tackleProb > 0) {
          actions.add(a);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);
          if (this.DEBUG) {
            System.out
                .println("Opponent can tackle in 1 Prob:" + tackleProbDashing + " maxProb:" + tackleProb + " Action:" + a);
          }
        }

        if (tackler.getActions().size() > 0) {
          this.oppsTackling.add(tackler);
        }
      }
    }
    // the world is bs based. opponent has 2 actions before tackling
    // check turning+dashing and 2x turning and 2x dashing
    else {
      double tackleProbTurning2x = 0.0;
      double tackleProbDashing2x = 0.0;
      double tackleProbDashingTurning = 0.0;
      double tackleProbTurningDashing = 0.0;

      for (final Player opp : opponents) {
        final TacklingPlayer tackler = new TacklingPlayer(opp);
        final LinkedList<Action> actions = new LinkedList<Action>();

        // 2x truning
        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);
        b = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(b);

        tackleProbTurning2x = this.dummyPlayer.tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbTurning2x);

        if (tackleProb > 0) {
          actions.add(a);
          actions.add(b);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);

          if (this.DEBUG) {
            System.out
                .println("Opponent " + opp.getNumber() + " can tackle Prob:" + tackleProbTurning2x + " maxProb:" + tackleProb + " Action1:" + a + " Action2:" + b);
          }
        }

        // 2x dashing
        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);
        b = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(b);

        tackleProbDashing2x = this.dummyPlayer.tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbDashing2x);

        if (tackleProb > 0) {
          actions.add(a);
          actions.add(b);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);

          if (this.DEBUG) {
            System.out
                .println("Opponent " + opp.getNumber() + " can tackle Prob:" + tackleProbDashing2x + " maxProb:" + tackleProb + " Action1:" + a + " Action2:" + b);
          }
        }

        // dashing + turning
        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);
        b = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(b);

        tackleProbDashingTurning = this.dummyPlayer
            .tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbDashingTurning);

        if (tackleProb > 0) {
          actions.add(a);
          actions.add(b);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);

          if (this.DEBUG) {
            System.out
                .println("Opponent " + opp.getNumber() + " can tackle Prob:" + tackleProbDashingTurning + " maxProb:" + tackleProb + " Action1:" + a + " Action2:" + b);
          }
        }

        // turning + dashing
        this.dummyPlayer.copy(opp);
        a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(a);
        b = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
        this.dummyPlayer.predictThisAfterAction(b);

        tackleProbTurningDashing = this.dummyPlayer
            .tackleSuccessProbability(point);
        tackleProb = Math.max(tackleProb, tackleProbTurningDashing);

        if (tackleProb > 0) {
          actions.add(a);
          actions.add(b);
          tackler.addTacklePossibility(this.dummyPlayer.cloned(), actions,
              tackleProb);

          if (this.DEBUG) {
            System.out
                .println("Opponent " + opp.getNumber() + " can tackle Prob:" + tackleProbTurningDashing + " maxProb:" + tackleProb + " Action1:" + a + " Action2:" + b);
          }
        }

        if (tackler.getActions().size() > 0) {
          this.oppsTackling.add(tackler);
        }
      }
    }

    return tackleProb > 0.0;
  }

  /**
   * checks if the ball resulting from our bestAction may result in the tackling
   * area of an opponent. If so it tries to move the ball outside the opponents
   * tackle area.
   */
  private void checkForTackling() {

    if ((this.benefit == 0.0) || (this.lastType == TYPE.SAVING_UNSECURE) || (this.lastType == TYPE.SAVING_SECURE)) {
      return;
    }

    final LinkedList<Vektor> points = new LinkedList<Vektor>();

    final Ball ball1 = this.getWorld().getBallRef().predictBallAfterAction(
        this.getWorld().getSelfRef(), this.bestAction);
    points.add(ball1.getPosition());

    final LinkedList<Vektor> goodPoints = this
        .checkPositionsAgainstTackling(points);

    if (goodPoints.size() == 1) {
      if (this.DEBUG) {
        System.out.println("Opponent can't tackle");
      }
      return;
    }

    if (this.DEBUG) {
      System.out.print("Opponent can tackle -> saving ball..");
    }

    this.saveBallFromOpponent();
  }

  /**
   * checks if the ball results outside and perfoms the action to keep it inside
   * the field
   */
  private void checkBallOutside() {

    final Ball resultingBall = this.getWorld().getBallRef()
        .predictBallAfterAction(this.getWorld().getSelfRef(), this.bestAction);
    if (!this.getWorld().inField(resultingBall.getPosition())) {
      if (this.DEBUG) {
        System.out.print("Next Ball outside!! ");
      }

      this.benefit *= 0.5;

      // opponent might get it in 1
      if ((this.lastType == TYPE.SAVING_SECURE) || (this.lastType == TYPE.SAVING_UNSECURE)) {
        // near own goal
        if (this.nextSelf.getDistance(SConf.getInstance().GOAL_POS_OWN) < 20) {
          // let ball going out
          if (resultingBall.getPosition().x > 8) {
            if (this.DEBUG) {
              System.out.print(" -> let it go (near goal).");
            }
            return;
          }
          // get ball away from were it is
          else {
            this.benefit *= 0.2;

            // try to get the ball in front of the player so we will
            // have max kick and tackle power in the next cycle
            this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
            this.dummyVektor.addToThis(this.nextSelf.getPosition());
            if (this.getWorld().inField(this.dummyVektor)) {

              this.subStateString = DribbleSituation.DEBUG_KICK_TO_FRONT;
              this.bestAction = BasicActionFactory.kickInTime(this.getWorld()
                  .getSelfRef(), this.getWorld().getBallRef(),
                  this.dummyVektor, 1, 0);
              if (this.DEBUG) {
                System.out
                    .print(" -> kicking in front (near goal)." + this.bestAction);
              }
              if (this.bestAction == null) {
                this.dummyVektor.x = 20 * Math.signum(this.nextSelf
                    .getPosition().x);
                this.dummyVektor.y = -45;
                this.subStateString = DribbleSituation.DEBUG_KICK_TO_SIDE;
                this.bestAction = BasicActionFactory.kickFastest(this
                    .getWorld().getSelfRef(), this.getWorld().getBallRef(),
                    this.dummyVektor);
                if (this.DEBUG) {
                  System.out
                      .print(" -> kicking to side (near goal)" + this.bestAction + this.dummyVektor);
                }
              }
            }
            // if the player is outside we kick the ball away to the
            // side
            else {
              this.dummyVektor.x = 20 * Math
                  .signum(this.nextSelf.getPosition().x);
              this.dummyVektor.y = -45;
              this.subStateString = DribbleSituation.DEBUG_WE_ARE_OUT;
              this.bestAction = BasicActionFactory
                  .kickFastest(this.getWorld().getSelfRef(), this.getWorld()
                      .getBallRef(), this.dummyVektor);
              if (this.DEBUG) {
                System.out
                    .print(" -> kicking to side (near goal)." + this.bestAction);
              }
            }
          }
        }
        // ball not near own goal
        else {
          this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
          this.dummyVektor.addToThis(this.nextSelf.getPosition());
          if (this.getWorld().inField(this.dummyVektor)) {
            this.subStateString = DribbleSituation.DEBUG_NOT_NEAR_GOAL;
            this.bestAction = BasicActionFactory.kickInTime(this.getWorld()
                .getSelfRef(), this.getWorld().getBallRef(), this.dummyVektor,
                1, 0);
            if (this.DEBUG) {
              System.out.println("-> kicking in front" + this.bestAction);
            }
            if (this.bestAction == null) {
              this.subStateString = DribbleSituation.DEBUG_STOPBALL;
              this.bestAction = this.stopBall();
              if (this.DEBUG) {
                System.out.println("-> stopping ball" + this.bestAction);
              }
            }
          }
          else {
            this.subStateString = DribbleSituation.DEBUG_STOPBALL;
            this.bestAction = this.stopBall();
            if (this.DEBUG) {
              System.out.println("-> stopping ball" + this.bestAction);
            }
          }
        }
      }
      // we have time
      else {
        this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());
        if (this.getWorld().inField(this.dummyVektor)) {
          this.subStateString = DribbleSituation.DEBUG_KICK_TO_FRONT;
          this.bestAction = BasicActionFactory.kickInTime(this.getWorld()
              .getSelfRef(), this.getWorld().getBallRef(), this.dummyVektor, 1,
              0);
          if (this.DEBUG) {
            System.out.println("-> kicking in front" + this.bestAction);
          }
          if (this.bestAction == null) {
            this.subStateString = DribbleSituation.DEBUG_STOPBALL;
            this.bestAction = this.stopBall();
            if (this.DEBUG) {
              System.out.println("-> stopping ball" + this.bestAction);
            }
          }
        }
        else {
          this.subStateString = DribbleSituation.DEBUG_STOPBALL;
          this.bestAction = this.stopBall();
          if (this.DEBUG) {
            System.out.println("-> stopping ball" + this.bestAction);
          }
        }
      }
    }
  }

  /**
   * @return true if we can get the next ball without a kick.
   */
  private boolean nextballCanBeReached() {

    final Action dash = BasicActionFactory.dashToPoint(this.getWorld()
        .getSelfRef(), this.nextBall.getPosition());

    return this.getWorld().getSelfRef().predictPlayerAfterAction(dash).canKick(
        this.nextBall);
  }

  /**
   * turns to the best dir. stops the ball first if needed.
   */
  private void doTurning() {

    if (this.DEBUG) {
      System.out.print("Turning ");
    }
    final boolean nextBallIsKickableAndSlow = (this.nextBall.getSpeed()
        .getLength() < 0.4) && (this.nextSelf.getDistance(this.nextBall) < 0.8);

    if (nextBallIsKickableAndSlow && !this.opponentCanTackleIn1) {
      this.bestAction = BasicActionFactory.turnBodyToPoint(this.getWorld()
          .getSelfRef(), this.pointToGo);
      this.setBenefit(TYPE.TURNING, this.getSecurity(TYPE.TURNING));
      if (this.DEBUG) {
        System.out.println(" to " + this.pointToGo);
      }
      this.subStateString = DribbleSituation.DEBUG_TURNING;
    }
    else {
      this.bestAction = this.stopBall();
      this.subStateString = DribbleSituation.DEBUG_STOPBALL;
      this.setBenefit(TYPE.TURNING, this.getSecurity(TYPE.TURNING));
      if (this.DEBUG) {
        System.out.println(" stopping ball first");
      }
    }
  }

  /**
   * tries to find a secure point assuming that the opponents will try to tackle
   * the ball in 1
   */
  private void saveBallFromOpponent() {

    final LinkedList<Player> playersThatCanKickIn1 = new LinkedList<Player>();

    for (final Player p : this.getProphet().getFastestPlayerToBall()) {
      if (!p.isFriend()) {
        if (this.getProphet().getInterceptionTime(p) == 1) {
          playersThatCanKickIn1.add(p);
        }
        else {
          break;
        }
      }
    }

    if (this.DEBUG) {
      System.out.println("Saving Ball from Opponents: ");
      for (final Player opp : playersThatCanKickIn1) {
        System.out.println("KickingIn1:" + opp + this.getProphet()
            .getNextInterceptAction(opp));
      }
      for (final TacklingPlayer opp : this.oppsTackling) {
        System.out.print("TacklingIn1:" + opp.getPlayer());
        for (final LinkedList<Action> actions : opp.getActions()) {
          for (final Action a : actions) {
            System.out.print(a);
          }
          System.out.print(" - ");
        }
        System.out.print(" || ");
      }

    }

    // the benefit decreases when opponent might get thte ball
    this.defaultBenefit /= (1 + playersThatCanKickIn1.size() + this.oppsTackling
        .size());
    if (this.bestBenefit > this.defaultBenefit) {
      this.benefit = -this.bestBenefit;
      this.bestAction = null;
      this.subStateString = "noKickBecauseOfPasses";

      if (this.DEBUG) {
        System.out
            .println("No Saving as passBenefit to high! -> Best: " + this.bestBenefit + " Dribble:" + this.defaultBenefit + "");
      }
      return;
    }

    // now we generate a number of test points
    // each tackling approach is answered by moving the ball away from the
    // opponent
    final LinkedList<Vektor> testPoints = new LinkedList<Vektor>();

    for (final TacklingPlayer opp : this.oppsTackling) {
      for (int move = 0; move < opp.getActions().size(); move++) {
        final Action a = opp.getActions().get(move).getLast();
        switch (a.getType()) {
          case TURN:
            final Vektor pos = opp.getP_tackling().get(move).getVektorTo(
                this.nextSelf);
            pos.setLength(0.9);
            testPoints.add(pos);
            if (this.DEBUG) {
              System.out.print("Added save point " + pos);
            }
            break;

          case DASH:
            this.dummyVektor = opp.getP_tackling().get(move).getPosRel(
                this.nextSelf, RELATIVE.TO_BODY);
            this.dummyVektor2.pointAtPolar(0.9, Vektor
                .normalize(opp.getPlayer().getBodyDir() + Math
                    .signum(this.dummyVektor.x) * 90.0));
            this.dummyVektor2.addToThis(this.nextSelf.getPosition());
            testPoints.add(this.dummyVektor2.cloned());
            if (this.DEBUG) {
              System.out.print("Added save point " + this.dummyVektor);
            }
            break;
        }
      }
    }

    // checking the new points
    final LinkedList<Vektor> goodPoints = this
        .checkPositionsAgainstTackling(testPoints);

    if (goodPoints.size() == 0) {
      if (this.mostSecurePoint.isEmpty()) {
        this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());
        if (this.DEBUG) {
          System.out
              .println("No secure point found -> kicking in front of player" + this.dummyVektor);
        }
      }
      else {
        this.dummyVektor.copy(this.mostSecurePoint);
        if (this.DEBUG) {
          System.out
              .println("Most secure point found -> kicking to" + this.mostSecurePoint);
        }

      }
      this.setBenefit(TYPE.SAVING_UNSECURE, 1);
      this.subStateString = DribbleSituation.DEBUG_SAVINGSECURE;
    }
    else {
      this.dummyVektor.copy(goodPoints.get(0));
      this.setBenefit(TYPE.SAVING_SECURE, 1);
      this.subStateString = DribbleSituation.DEBUG_SAVINGUNSECURE;

      if (this.DEBUG) {
        System.out
            .println("Secure point found -> kicking to" + this.dummyVektor);
      }

    }

    this.bestAction = BasicActionFactory.kickInTime(this.getWorld()
        .getSelfRef(), this.getWorld().getBallRef(), this.dummyVektor, 1, 0.2);

    // we can not kick to the save point
    if (this.bestAction == null) {
      // if in danger kick the ball away, otherwise stop it
      if (this.nextSelf.getDistance(SConf.getInstance().GOAL_POS_OWN) > 20) {
        this.subStateString = DribbleSituation.DEBUG_STOPBALL;
        this.bestAction = this.stopBall();
      }
      else {
        this.subStateString = DribbleSituation.DEBUG_CLEAR;
        this.bestAction = BasicActionFactory.kickFastest(this.getWorld()
            .getSelfRef(), this.getWorld().getBallRef(),
            SConf.getInstance().GOAL_POS_OTHER);
      }
    }

    if (this.DEBUG) {
      System.out.println("KICK: " + this.bestAction);
    }

  }

  /**
   * checks a given number of points against tackling possiblities. this methods
   * assumes, that the opponents try to tackle the next ball, and is therefore
   * based on the tackler list
   * 
   * @param testPoints -
   *          the points to check
   */
  private LinkedList<Vektor> checkPositionsAgainstTackling(final LinkedList<Vektor> testPoints) {

    final LinkedList<Vektor> goodPoints = new LinkedList<Vektor>();
    for (final Vektor v : testPoints) {
      goodPoints.add(v);
    }

    double lowestTackleProb = 0.8;
    double tackleProb;

    for (final TacklingPlayer opp : this.oppsTackling) {
      for (final Player p : opp.p_tackling) {
        for (final Vektor point : testPoints) {
          tackleProb = p.tackleSuccessProbability(point);

          if (tackleProb < lowestTackleProb) {
            lowestTackleProb = tackleProb;
            this.mostSecurePoint = point;
          }

          if (tackleProb > 0.2) {
            if (this.DEBUG) {
              System.out.println(point + " can be tackled by" + p);
            }
            goodPoints.remove(point);
          }
        }
      }
    }

    return goodPoints;
  }

  /**
   * dribbles fast
   * 
   */
  private void doFastDribbling() {

    if (this.DEBUG) {
      System.out.println("FAST DRIBBLING");
    }

    if (this.setBenefit(TYPE.FAST_DRIBBLING, this
        .getSecurity(TYPE.FAST_DRIBBLING))) {
      this.subStateString = DribbleSituation.DEBUG_FASTDRIBBLING;
      this.bestAction = this.getDribbleAction(TYPE.FAST_DRIBBLING);
    }
  }

  /**
   * dribbles slow
   * 
   */
  private void doSlowDribbling() {

    if (this.DEBUG) {
      System.out
          .println(this.getWorld().getCycleAndPlayerString() + "SLOW DRIBBLING");
    }

    if (this.setBenefit(TYPE.SLOW_DRIBBLING, this
        .getSecurity(TYPE.SLOW_DRIBBLING))) {
      this.bestAction = this.getDribbleAction(TYPE.SLOW_DRIBBLING);
      this.subStateString = DribbleSituation.DEBUG_SLOWDRIBBLING;
    }
  }

  /**
   * the side to put the ball on when dribbling (depends on the players role and
   * on the side of the nearest opponents).
   * 
   * @return -1 = left, 1 = right (to dribble dir)
   */
  private int getDribbleSide() {

    // opponents further away than hat are not considered
    final double tolerance = 3;

    // on left side put ball more often right for better passing
    if (this.nextBall.getPosition().x < -10) {
      if (this.oppsRight.size() == 0) {
        return 1;
      }
      if (this.oppsLeft.size() == 0) {
        return this.nextSelf.getDistance(this.oppsRight.get(0)) > tolerance ? 1 : -1;
      }

      final double distOppRight = this.nextSelf.getDistance(this.oppsRight
          .get(0));
      final double distOppLeft = this.nextSelf
          .getDistance(this.oppsLeft.get(0));

      return distOppRight > distOppLeft - tolerance ? 1 : -1;
    }

    // on right side put ball more often left for better passing
    else if (this.nextBall.getPosition().x > 10) {
      if (this.oppsLeft.size() == 0) {
        return -1;
      }
      if (this.oppsRight.size() == 0) {
        return this.nextSelf.getDistance(this.oppsLeft.get(0)) > tolerance ? -1 : 1;
      }

      final double distOppRight = this.nextSelf.getDistance(this.oppsRight
          .get(0));
      final double distOppLeft = this.nextSelf
          .getDistance(this.oppsLeft.get(0));

      return distOppLeft > distOppRight - tolerance ? -1 : 1;
    }

    // in center put ball were it is more secure
    else {
      final double distOppRight = this.oppsRight.size() == 0 ? 10 : Math.min(
          this.nextSelf.getDistance(this.oppsRight.get(0)), 7);
      final double distOppLeft = this.oppsLeft.size() == 0 ? 10 : Math.min(
          this.nextSelf.getDistance(this.oppsLeft.get(0)), 7);

      double ballIsRight = this.getWorld().getSelfRef().getPosRel(
          this.getWorld().getBallRef(), RELATIVE.TO_BODY).x;

      if (ballIsRight > 0.3) {
        ballIsRight = 1;
      }
      else if (ballIsRight < -0.3) {
        ballIsRight = -1;
      }
      else {
        ballIsRight = 0;
      }

      return distOppLeft > distOppRight + ballIsRight * 2 ? -1 : 1;
    }

  }

  /**
   * dribbles in body dir. the speed is limited by a given dashAction.
   * 
   * @param type -
   *          the dribble type
   * @return the best dribble action
   */
  private Action getDribbleAction(final TYPE type) {

    final DashAction dashAction = type == TYPE.FAST_DRIBBLING ? this.dash100 : this.dash50;

    final DashAction maxDash = BasicActionFactory.dashToPoint(this.getWorld()
        .getSelfRef(), this.nextBall.getPosition());

    // the dashaction corrected to the Stamina level
    final DashAction correctedDash = this.dummyDashAction;
    correctedDash.setPower(dashAction.getPower());

    // save more stamina when dribbling slow
    if (type == TYPE.SLOW_DRIBBLING) {
      this.nextSelf.correctDashPower(correctedDash, STAMINA_LEVEL.MEDIUM);
    }
    else {
      this.nextSelf.correctDashPower(correctedDash, STAMINA_LEVEL.LOW);
    }

    maxDash.setPower(Math.min(maxDash.getPower(), correctedDash.getPower()));

    // the player after dashing 1x
    final Player p_dashing = this.getWorld().getSelfRef()
        .predictPlayerAfterAction(maxDash);

    // next ball will be kcikable for p_dashing
    final boolean nextBallIsKickable = p_dashing.getDistance(this.nextBall) < 0.2 + this
        .getWorld().getSelfRef().getPConf().KICKABLE_MARGIN;

    // if kickable try dashing to follow the ball
    if ((maxDash.getPower() > 0) && nextBallIsKickable && !this.opponentCanTackleIn1) {
      if (this.DEBUG) {
        System.out.print("Try dashing ");
      }
      final Line playerDashLine = new Line(this.nextSelf.getPosition(),
          this.nextSelf.getPosition().add(
              new Vektor(Vektor.POLAR, 1, this.nextSelf.getBodyDir())));

      final Circle minBallCircle = new Circle(this.nextBall.getPosition(), 0.5);

      final Circle maxBallCircle = new Circle(this.nextBall.getPosition(),
          this.nextSelf.getPConf().KICKABLE_MARGIN + 0.2);

      // try to get as close as possible (best when ball ahead)
      final LinkedList<Vektor> intersectionPoints = playerDashLine
          .getIntersectionPoints(minBallCircle);

      // try to get as far as possible (best when ball behind)
      intersectionPoints.addAll(playerDashLine
          .getIntersectionPoints(maxBallCircle));

      DashAction simDash = null;
      DashAction bestDash = null;

      // get the dash action with the max power
      for (final Vektor ip : intersectionPoints) {
        simDash = BasicActionFactory.dashToPoint(this.getWorld().getSelfRef(),
            ip);

        switch (type) {
          case FAST_DRIBBLING:
            if (simDash.getPower() > correctedDash.getPower() - 20) {
              if ((bestDash == null) || (bestDash.getPower() < simDash
                  .getPower())) {
                bestDash = simDash;
              }
            }
            continue;

          case SLOW_DRIBBLING:
            if (simDash.getPower() > correctedDash.getPower() - 20) {
              if ((bestDash == null) || (bestDash.getPower() < simDash
                  .getPower())) {
                bestDash = simDash;
              }
            }
            continue;
        }
      }

      if (bestDash != null) {
        if (this.DEBUG) {
          System.out.println("Dashing " + bestDash);
        }
        return bestDash;
      }
    }

    // no dashing -> try kicking
    final int dribbleSide = this.getDribbleSide();
    Action a = null;
    Vektor bestKickPosAbs1 = null;
    Vektor bestKickPosAbs2 = null;
    Vektor bestKickPosAbs3 = null;
    Vektor bestKickPosRel1 = null;
    Vektor bestKickPosRel2 = null;
    Vektor bestKickPosRel3 = null;

    switch (type) {

      // TODO should there be any differences here?? (rw)
      case SLOW_DRIBBLING:
      case FAST_DRIBBLING:

        // best distance to player
        final double r_1 = 0.75;

        // best x offset to player center
        final double l_best = 0.6;

        // best y offset to player center
        final double halfBallWay = Math.sqrt(r_1 * r_1 - l_best * l_best);

        // best rel. kick pos
        bestKickPosRel1 = new Vektor(Vektor.POLAR, l_best, dribbleSide * 90.0)
            .add(new Vektor(Vektor.POLAR, halfBallWay * 0.9, -180.0));

        // best rel. kick pos
        bestKickPosRel2 = bestKickPosRel1.cloned();
        bestKickPosRel3 = bestKickPosRel1.cloned();

        final double pSize = this.nextSelf.getPConf().PLAYER_SIZE;
        final double kickDist = this.nextSelf.getKickDistance();
        bestKickPosRel1.setLength(pSize + (kickDist - pSize) * 0.6);
        bestKickPosRel2.setLength(pSize + (kickDist - pSize) * 0.75);
        bestKickPosRel3.setLength(pSize + (kickDist - pSize) * 0.85);

        int i = 1;
        KickAction simKick1;
        KickAction simKick2;
        KickAction simKick3;
        final Ball simBall1 = Ball.getDummyInstance();
        final Ball simBall2 = Ball.getDummyInstance();
        final Ball simBall3 = Ball.getDummyInstance();
        final Player simPlayer = Player.getDummyInstance();
//        final DashAction simDash = new DashAction(0);

        final Player p_ = this.nextSelf.cloned();

        boolean goodTry1 = false;
        boolean goodTry2 = false;
        boolean goodTry3 = false;

        // this loop simulates ball and player after a kick
        // 1. we try to get the ball to the bestAbsPosition in
        // 'i' cycles
        // 2. we check if the resulting ball will always be in a
        // certain range
        // of the player
        // assuming the player dashes for the ball
        // 3. if(2.) -> remember the kickAction
        // 4. try to kick to a position that is farer away so we
        // can dash more
        // -> i++ -> 1
        while (i < 10) {
          bestKickPosAbs1 = p_.getPosAbs(bestKickPosRel1,
              Player.RELATIVE.TO_BODY);
          simKick1 = BasicActionFactory.kickInTime(
              this.getWorld().getSelfRef(), this.getWorld().getBallRef(),
              bestKickPosAbs1, i, 0.1);

          bestKickPosAbs2 = p_.getPosAbs(bestKickPosRel2,
              Player.RELATIVE.TO_BODY);
          simKick2 = BasicActionFactory.kickInTime(
              this.getWorld().getSelfRef(), this.getWorld().getBallRef(),
              bestKickPosAbs2, i, 0.1);

          bestKickPosAbs3 = p_.getPosAbs(bestKickPosRel3,
              Player.RELATIVE.TO_BODY);
          simKick3 = BasicActionFactory.kickInTime(
              this.getWorld().getSelfRef(), this.getWorld().getBallRef(),
              bestKickPosAbs3, i, 0.1);

          simBall1.copy(this.getWorld().getBallRef());
          simBall1.predictThisAfterAction(this.getWorld().getSelfRef(),
              simKick1);

          simBall2.copy(this.getWorld().getBallRef());
          simBall2.predictThisAfterAction(this.getWorld().getSelfRef(),
              simKick2);

          simBall3.copy(this.getWorld().getBallRef());
          simBall3.predictThisAfterAction(this.getWorld().getSelfRef(),
              simKick3);

          int j = 1;
          simPlayer.copy(this.nextSelf);// =
          // world.getSelfRef().predictPlayerAfterAction(new
          // KickAction(world.isViBased(),0,0.0));
          goodTry1 = true;
          while (j <= i) {
            if (simPlayer.getDistance(simBall1) > kickDist - 0.05) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 1 too far");
              }
              goodTry1 = false;
              break;
            }
            else if (simPlayer.getDistance(simBall1) < pSize + 0.075) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 1 too close");
              }
              goodTry1 = false;
              break;
            }
            simPlayer.predictThisAfterAction(correctedDash);
            simBall1.extrapolate(1);
            j++;
          }

          j = 1;
          simPlayer.copy(this.nextSelf);
          goodTry2 = true;
          while (j <= i) {
            if (simPlayer.getDistance(simBall2) > kickDist - 0.05) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 2 too far");
              }
              goodTry2 = false;
              break;
            }
            else if (simPlayer.getDistance(simBall2) < pSize + 0.075) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 2 too close");
              }
              goodTry2 = false;
              break;
            }
            simPlayer.predictThisAfterAction(correctedDash);
            simBall2.extrapolate(1);
            j++;
          }

          j = 1;
          simPlayer.copy(this.nextSelf);
          goodTry3 = true;
          while (j <= i) {
            if (simPlayer.getDistance(simBall3) > kickDist - 0.05) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 3 too far");
              }
              goodTry3 = false;
              break;
            }
            else if (simPlayer.getDistance(simBall3) < pSize + 0.075) {
              if (DEBUG2) {
                System.out.println(getWorld().getCycleAndPlayerString()+" ball 3 too close");
              }
              goodTry3 = false;
              break;
            }
            simPlayer.predictThisAfterAction(correctedDash);
            simBall3.extrapolate(1);
            j++;
          }

          if (goodTry1) {
            a = simKick1;
            if (DEBUG2) {
              System.out.println(getWorld().getCycleAndPlayerString()+" ball 1 good");
            }
            if (this.DEBUG) {
              System.out
                  .println("Could kick to " + bestKickPosAbs1 + this.bestAction + correctedDash);
            }
          }
          else if (goodTry2) {
            a = simKick2;
            if (DEBUG2) {
              System.out.println(getWorld().getCycleAndPlayerString()+" ball 2 good");
            }
            if (this.DEBUG) {
              System.out
                  .println("Could kick to " + bestKickPosAbs2 + this.bestAction + correctedDash);
            }
          }
          else if (goodTry3) {
            a = simKick3;
            if (DEBUG2) {
              System.out.println(getWorld().getCycleAndPlayerString()+" ball 3 good");
            }
            if (this.DEBUG) {
              System.out
                  .println("Could kick to " + bestKickPosAbs3 + this.bestAction + correctedDash);
            }
          }
          i++;
          p_.predictThisAfterAction(correctedDash);
        }
    }

    // ball position is bad kicking in dribble direction
    // -> put it to a good position
    if (a == null) {
      this.dummyVektor.pointAt(0.5 * dribbleSide, -0.3);
      this.dummyVektor.copy(this.nextSelf.getPosAbs(this.dummyVektor,
          RELATIVE.TO_BODY));
      this.subStateString += "BadKick";
      a = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(), this
          .getWorld().getBallRef(), this.dummyVektor, 1, 0.1);

      if (this.DEBUG) {
        if (a == null) {
          System.out.println(" No kicking to bestPos " + this.dummyVektor);
        }
        else {
          System.out.println(" Kicking to bestPos " + this.dummyVektor);
        }
      }
    }

    // we could not even bring the ball to a position where would could kick
    // it in dribble dir the next cycle. As the ball is probably very fast we
    // try
    // to stop it
    if (a == null) {

      // try to stop the ball
      this.subStateString += "StopBall";
      a = this.stopBall();

      if (this.DEBUG) {
        System.out
            .println(" Stopping ball." + a + bestKickPosAbs1 + bestKickPosRel1 + dribbleSide);
      }
    }
    else {
      if (this.DEBUG) {
        System.out
            .println(" kicking ball." + a + bestKickPosAbs1 + bestKickPosRel1 + dribbleSide);
      }
    }

    return a;
  }

  /**
   * outplays the goalie
   * 
   * TODO all
   */
  private void outplayGoalie() {

    if (this.DEBUG) {
      System.out.println("OUTPLAYING GOALIE");
      this.subStateString = DribbleSituation.DEBUG_OUTPLAYGOALIE;
    }
    if (this.gotWrongBodyDir()) {
      this.doTurning();
    }
    else {
      this.doFastDribbling();
    }
  }

  /**
   * waits with the ball and lets it circulated around the player which might
   * create better pass situations as the initiale ball speed and positions
   * vary. If a wing player the side to the field is prefered as its more
   * probable to find someone to pass to in the field's center.
   */
  private void waitWithBall() {

    if (!this.setBenefit(TYPE.WAIT_WITH_BALL, this
        .getSecurity(TYPE.WAIT_WITH_BALL))) {
      return;
    }

    final boolean nextBallIsKickableAndSlow = (this.nextBall.getSpeed()
        .getLength() < 0.3) && (this.nextSelf.getDistance(this.nextBall) < 0.7);
    final boolean shouldTurn = this.gotWrongBodyDir();

    // next ball can be handled -> turn each second cycle
    if (nextBallIsKickableAndSlow && shouldTurn) {
      this.dummyVektor.pointAtPolar(10, this.getNextTurnDir());
      this.dummyVektor.addToThis(this.nextSelf.getPosition());
      this.bestAction = BasicActionFactory.turnBodyToPoint(this.getWorld()
          .getSelfRef(), this.dummyVektor);
      if (this.DEBUG) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + " Dribbling - Wait: Turning" + this.bestAction);
      }
      this.subStateString = DribbleSituation.DEBUG_WAITWITHBALL;
    }
    // circulate ball otherwise
    else {
      if (shouldTurn) {
        this.subStateString = DribbleSituation.DEBUG_STOPBALL;
        this.bestAction = this.stopBall();
      }
      else {
        this.subStateString = DribbleSituation.DEBUG_CIRCULATEBALL;
        this.bestAction = this.circulateBall();
      }
    }
  }

  /**
   * @return true if our body dir differs (much) form the dir to go to
   */
  private boolean gotWrongBodyDir() {

    if (this.getWorld().getSelfRef().getPosition().y > SConf.getInstance().half_length - 15) {
      return Math.abs(this.nextSelf.getAngleRel(this.pointToGo,
          RELATIVE.TO_BODY)) > 30;
    }
    else {
      return Math.abs(this.nextSelf.getAngleRel(this.pointToGo,
          RELATIVE.TO_BODY)) > 20;
    }
  }

  /**
   * @return the next dir to turn to when waiting with the ball
   */
  private double getNextTurnDir() {

    final double dirToGoal = this.nextSelf
        .getAngleTo(SConf.getInstance().GOAL_POS_OTHER);

    double[] dirs;

    if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
        .getRoleByRoleNumber())) {
      dirs = new double[] { 0, 90, dirToGoal };
    }
    else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
        .getRoleByRoleNumber())) {
      dirs = new double[] { 0, -90, dirToGoal };
    }
    else {
      dirs = new double[] { -90, 0, 90, dirToGoal };
    }

    final int idx = (int) (Math.random() * dirs.length);
    double dir = dirs[idx];
    if (Math.abs(this.nextSelf.getAngleRel(dir, RELATIVE.TO_BODY)) < 20) {
      dir = dirs[(idx + 1) % dirs.length];
    }

    return dir;

  }

  /**
   * circulates the ball around the player.
   * 
   * @see #waitWithBall()
   */
  private Action circulateBall() {

    this.subStateString = DribbleSituation.DEBUG_CIRCULATEBALL;
    // we collided. As the ball during circulation should be slow we
    // prefer not kicking it instead of doing a wrong kick due to the underlying
    // noisy information
    if (this.getWorld().collided() && !this.getWorld().isViBased()) {
      System.out.println("Circulate Ball collided in BS-Mode -> no acting");
      return new KickAction(0, 0);
    }
    Action a;

    final double angleToBall = this.getWorld().getSelfRef().getAngleRel(
        this.getWorld().getBallRef(), RELATIVE.TO_BODY);
    final Vektor bestPos = this.nextSelf.getPosition().cloned();

    if ((Math.abs(angleToBall) > 45) && (Math.abs(angleToBall) < 135)) {
      this.dummyVektor.pointAtPolar(0.7, this.nextSelf.getBodyDir());
    }
    else {
      final int dribbleSide = this.getDribbleSide();

      final double limit = dribbleSide == 1 ? 0.25 : 0.75;

      if (Math.random() > limit) {
        this.dummyVektor.pointAtPolar(0.7, this.nextSelf.getBodyDir() + 45);
      }
      else {
        this.dummyVektor.pointAtPolar(0.7, this.nextSelf.getBodyDir() - 45);
      }
    }

    bestPos.addToThis(this.dummyVektor);

    a = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(), this
        .getWorld().getBallRef(), bestPos, 1, 0.2);

    // the kick to the bestPos isn't posiible -> stop the ball
    if ((a == null) || !this.getWorld().inField(bestPos)) {
      a = this.stopBall();
      if (this.DEBUG) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + "Dribbling - Alterating Ball STOPPING" + a + bestPos);
      }
    }
    else {
      if (this.DEBUG) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + "Dribbling - Alterating Ball" + a + bestPos);
      }
    }

    return a;
  }

  /**
   * stops the ball (or at least slows it down as best as possible)
   * 
   * @return an action that stops the ball, if possible the ball is stopped in
   *         front of the player
   * 
   * TODO - move to action factories
   */
  private Action stopBall() {

    this.subStateString = DribbleSituation.DEBUG_STOPBALL;
    final Ball b = this.getWorld().getBallRef();
    final Player self = this.getWorld().getSelfRef();

    if (this.DEBUG) {
      System.out
          .print(this.getWorld().getCycleAndPlayerString() + " Trying to stop ball:");
    }

    // try to stop ball
    KickAction kickAction = null;

    // trying to collide as we are heading towards the wrong direction
    if (this.gotWrongBodyDir() && (this.nextSelf.getSpeed().getLength() > 0.1)) {
      this.dummyVektor.pointAtPolar(0.1, this.nextSelf.getBodyDir());
      this.dummyVektor.addToThis(this.nextSelf.getPosition());

      kickAction = BasicActionFactory.kickInTime(self, b, this.dummyVektor, 1,
          0.3);
      if (this.DEBUG && (kickAction != null)) {
        System.out
            .print(" kicking inside player " + kickAction + " Speed:" + self
                .getSpeed().getLength());
      }

      if (kickAction != null) {
        return kickAction;
      }
    }

    // get the point in front of the player in 3 cycles
    // 1 cycle kicking + 2 cycles turning if needed
    // (the extra cycle slows down the ball)
    this.dummyVektor.copy(self.getSpeed());
    this.dummyVektor.multThis(this.nextSelf
        .relativePlayerMovementAfterNCycles(3));
    this.dummyVektor.addToThis(self.getPosition());
    this.dummyVektor.addToThis(new Vektor(0.6, this.nextSelf
        .getAngleTo(this.pointToGo)));

    kickAction = BasicActionFactory.kickInTime(self, b, this.dummyVektor, 3,
        0.3);

    if (this.DEBUG && (kickAction != null)) {
      System.out.print(" kicking to bestPos " + kickAction);
    }

    if (kickAction == null) {
      final double angleToBall = this.nextSelf.getAngleRel(this.nextBall,
          RELATIVE.TO_BODY);

      // best we can do ;-)
      kickAction = new KickAction(100, Vektor.normalize(180 + angleToBall));
      if (this.DEBUG) {
        System.out.print(" slowing down ball " + kickAction);
      }
    }

    return kickAction;
  }

  /**
   * the number of cycles an opponent needs to get the ball. (which is pretty
   * complicated to tell as we can move the ball around ;-))
   * 
   * @param type -
   *          the dribble type
   * @return the number of cycles an opponent needs to intercept us
   */
  private int getSecurity(final TYPE type) {

    int security = DribbleSituation.maxSecurity;

    switch (type) {
      case WAIT_WITH_BALL:
        if (this.oppsRelevant.size() > 0) {
          // distance of nearest opp
          security = (int) (this.getWorld().getSelfRef().getDistance(
              this.oppsRelevant.get(0)) - this.oppsRelevant.get(0)
              .getKickDistance());

          // -age of nearest opp (max = 2, as we feel near opponents)
          security -= Math.min(this.oppsRelevant.get(0).getAge(
              this.getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO),
              2);
        }
        break;

      case FAST_DRIBBLING:
      case SLOW_DRIBBLING:
        if (this.oppsInBodyDir.size() > 0) {
          // distance of nearest opp
          security = (int) (this.getWorld().getSelfRef().getDistance(
              this.oppsInBodyDir.get(0)) - this.oppsInBodyDir.get(0)
              .getKickDistance());

          // -age of nearest opp (max = 2, as we feel near opponents)
          security -= Math.min(this.oppsInBodyDir.get(0).getAge(
              this.getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO),
              2);
        }
        else if (this.oppsRelevant.size() > 0) {
          // distance of nearest opp
          security = (int) (1.4 * (this.getWorld().getSelfRef().getDistance(
              this.oppsRelevant.get(0)) - this.oppsRelevant.get(0)
              .getKickDistance()));

          // -age of nearest opp (max = 2, as we feel near opponents)
          security -= Math.min(this.oppsRelevant.get(0).getAge(
              this.getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO),
              2);
        }
        break;

      case SAVING_SECURE:
        return 1;
      case SAVING_UNSECURE:
        return 0;

      case BACKWARD_DRIBBLING:
      case TURNING:
        return this.oppsNear.size() == 0 ? DribbleSituation.maxSecurity : (int) this.oppsNear
            .get(0).getDistance(this.nextSelf.getPosition()) - 1;
    }

    if (this.opponentCanTackleIn1) {
      security--;
    }

    if (security < 0) {
      security = 0;
    }

    if (security > DribbleSituation.maxSecurity) {
      security = DribbleSituation.maxSecurity;
    }

    return security;
  }

  /**
   * sets the benefit for the situation. The benefit depends on the chosen
   * dribble behaviour and the security.
   * 
   * @param type -
   *          dibble type
   * @param security -
   *          the opponents interception time
   * @return true if dribble benefit > pass benefit
   */
  private boolean setBenefit(final TYPE type,
      final int security) {

    // the SituationEvaluator does not make a difference between
    // the different dribble behaviours. This is done here.
    switch (type) {
      case TURNING:
        this.benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.WAIT_WITH_BALL,
            this.getWorld().getSelfRef().getPosition(),
            this.getWorld().getSelfRef().getPosition(),
            Math.max(security - 1, 0), 1, this.getWorld().getSelfRef());
        break;

      case WAIT_WITH_BALL:
      case SAVING_SECURE:
        this.benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.WAIT_WITH_BALL,
            this.getWorld().getSelfRef().getPosition(),
            this.getWorld().getSelfRef().getPosition(),
            Math.max(security - 1, 0), 1, this.getWorld().getSelfRef()) * 0.3;
        break;

      case SAVING_UNSECURE:
        this.benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.WAIT_WITH_BALL,
            this.getWorld().getSelfRef().getPosition(),
            this.getWorld().getSelfRef().getPosition(),
            Math.max(security - 1, 0), 1, this.getWorld().getSelfRef()) * 0.1;
        break;

      case SLOW_DRIBBLING:
        this.dummyVektor.pointAtPolar(3, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());

        this.benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.WAIT_WITH_BALL,
            this.getWorld().getSelfRef().getPosition(), this.dummyVektor,
            Math.max(security - 1, 0), 1, this.getWorld().getSelfRef()) * 0.9;
        break;

      case FAST_DRIBBLING:
        this.dummyVektor.pointAtPolar(7, this.nextSelf.getBodyDir());
        this.dummyVektor.addToThis(this.nextSelf.getPosition());

        this.benefit = this.getSituationEvaluator().getValue(
            SituationEvaluator.TYPE.WAIT_WITH_BALL,
            this.getWorld().getSelfRef().getPosition(), this.dummyVektor,
            Math.max(security, 0), 1, this.getWorld().getSelfRef());
        break;

    }
    if (this.DEBUG) {
      System.out
          .println(type + " Benefit: " + this.benefit + " Best:" + this.passBenefit + " Sec:" + security);
    }

    this.lastType = type;

    return this.benefit > this.passBenefit;
  }

  /**
   * prints the opponent lists
   */
  @SuppressWarnings("unused")
  private void printOpponentLists() {

    System.out
        .println(this.getWorld().getCycleAndPlayerString() + " DribbleSituation. Consider opponents:" + this
            .getWorld().getSelfRef());

    System.out.print("Near: ");
    for (final Player o : this.oppsNear) {
      System.out.print(o.getNumber() + " ");
    }
    System.out.println();

    System.out.print("In Body Dir: ");
    for (final Player o : this.oppsInBodyDir) {
      System.out.print(o.getNumber() + " ");
    }
    System.out.println();

    System.out.print("In goal dir: ");
    for (final Player o : this.oppsInGoalDir) {
      System.out.print(o.getNumber() + " ");
    }
    System.out.println();

    System.out.print("In back dir: ");
    for (final Player o : this.oppsInBackDir) {
      System.out.print(o.getNumber() + " ");
    }
    System.out.println();
  }

  /**
   * @return the dribble type
   */
  public TYPE getType() {

    if (!this.wasUpdated()) {
      this.update();
    }

    return this.lastType;
  }

}
